iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
AI Engineering

AI Agent 系統開發 30 天系列 第 29 篇

用 Langfuse 建立 Agent 稽核紀錄與異常監控

  • 分享至 

  • xImage
  •  

在 用 Langfuse 建立 Agent 評估流程 與 用 Langfuse 從線上 Trace 評估 Agent 正確率 中,Langfuse 主要用來記錄模型輸入輸出與評估意圖分類的正確率。

當 Agent 開始接入退款、轉帳或修改資料等具有副作用的 Tool 時,觀測重點就要延伸到安全維運:

  • 稽核紀錄(Audit Trail):記錄每筆操作的完整決策脈絡。未授權的請求必須留下在呼叫 Tool 前即被政策阻擋的 Trace 證明;放行的請求則留下完整的執行與狀態紀錄。
  • 異常監控(Anomaly Monitoring):線上即時標記越權行為,統計非本人操作的發生頻率與趨勢,並在異常暴增時觸發告警。

這兩項機制的層次不同:業務安全政策(Guardrail)在事前檢查並阻擋未授權請求,確保敏感 Tool 根本不會被觸發;Langfuse 則在事後保存完整的決策鏈路,並透過 Score 與 Dashboard 呈現安全態勢。

安全決策鏈路與 Trace 結構

在安全架構中,模型判定要退款後,系統不能直接執行退款,而是要依序通過政策檢查與後端執行。

對應的範例程式位於 langgraph-pydantic-intent-routing。核心安全執行控制器位於 security_audit/runner.py,其中的 run_security_audit() 是主入口函式。無論是 Web API、自動化測試或命令列腳本,都呼叫這個函式來驅動一次完整的安全對話流程:

https://ithelp.ithome.com.tw/upload/images/20261010/201118963mtURyXbcd.png

三層防禦結構由外而內展開。其中 langfuse.start_as_current_observation(...) 是 Langfuse SDK 語法,只要用 with 包住程式碼,就會自動在 Trace 樹狀圖中新增一個監控節點(如 agent、guardrail 或 tool):

def run_security_audit(message, context):
    # ① 階段 1:最外層 Agent Trace(意圖辨識)
    with langfuse.start_as_current_observation(as_type="agent", name="customer-service-agent"):
        intent, order_id = classify_intent(message)
        if intent != "refund":
            return "一般回覆(非敏感操作,直接結束)"

        # ② 階段 2:Guardrail Trace(事前安全政策檢查)
        with langfuse.start_as_current_observation(as_type="guardrail", name="refund-policy"):
            if not check_policy(context, order_id):
                return "政策攔截:非本人訂單(事前中止,不呼叫 Tool)"

        # ③ 階段 3:Tool Trace(後端交易執行)
        with langfuse.start_as_current_observation(as_type="tool", name="process-refund"):
            return backend.process_refund(order_id)

整個架構的執行邏輯:辨識為敏感意圖才進 Guardrail ➔ 政策放行才進 Tool 執行。任何一步未放行就提早返回(Early Return),杜絕越權操作。

三個階段的核心實作細節

細看這三個階段在資安防護上的具體設計:

階段一:入口 Trace 與身分去識別化

在將請求送入 LangGraph 前,使用者 ID 不能直接以明文送往觀測平台。透過 _pseudonymous_user_id() 以 HMAC-SHA256 生成去識別化別名(usr_...),並注入 Trace 屬性中:

with langfuse.start_as_current_observation(as_type="agent", name="customer-service-agent"):
    with propagate_attributes(
        trace_name="customer-service-agent",
        user_id=_pseudonymous_user_id(context.session_user_id),
        session_id=context.session_id,
        environment=context.environment,
        metadata={"request_id": context.request_id},
    ):
        routing = invoke(graph, message, callbacks=[langfuse_handler])

階段二:意圖為退款時執行事前政策檢查(Guardrail)

當前發起請求的已登入身分(context.session_user_id)來自系統驗證(例如 JWT Token),不交由模型猜測。模型提取出 order_id 後,refund_policy.evaluate() 會向訂單資料庫執行唯讀查詢,比對訂單擁有者是否為當前登入者,並確認訂單狀態是否符合退款資格。

這項檢查由 guardrail 步驟記錄,並在 Metadata 存入固定原因代碼 reason_code。若未放行,流程直接返回拒絕訊息,絕對不呼叫退款 Tool:

with langfuse.start_as_current_observation(as_type="guardrail", name="refund-policy"):
    decision = refund_policy.evaluate(context, order_id)
    policy_span.update(
        output={"outcome": decision.outcome.value},
        metadata={"reason_code": decision.reason_code.value},
    )

if decision.outcome is not PolicyOutcome.ALLOW:
    return ...  # 政策未通過時直接事前結束,不進入 Tool

[!NOTE] 為什麼不讓後端直接擋就好,還要在 Agent 層多做 Guardrail?
後端 API 當然必須具備 403 權限防禦;但將檢查前置到 Agent 的 Guardrail 層有兩大關鍵原因:

  1. 在副作用發生前踩煞車:避免模型被 Prompt Injection 套話或產生幻覺後,直接去呼叫後端扣款 API。
  2. 獨立資安觀測:在 Langfuse 上獨立記錄 as_type="guardrail",能精確統計「被嘗試了哪些 Prompt 企圖越權」,而不是把資安攻擊混在一般的後端 Tool 錯誤中。

階段三:政策放行時執行退款 Tool

只有政策判定 allow 時,才建立 tool 步驟呼叫後端執行退款,並記錄執行狀態:

with langfuse.start_as_current_observation(as_type="tool", name="process-refund"):
    refund = refund_backend.process_refund(refund_args, context, decision)
    tool_span.update(output={"status": refund.status})

執行範例產生正常與異常紀錄

專案在 .env 中已設定好 Langfuse 連線金鑰、執行環境與使用者雜湊金鑰(LANGFUSE_USER_HASH_KEY)。

切換至專案目錄並安裝依賴:

cd ai-agent-sample/langgraph/langgraph-pydantic-intent-routing
uv sync

1. 執行正常放行請求

執行一筆本人訂單的退款請求(當前使用者擁有訂單 ORD-1001):

uv run agent-security-audit \
  "我要退款訂單 ORD-1001" \
  --request-id req-allow-1 \
  --idempotency-key refund-req-allow-1

終端機輸出顯示政策放行並執行 Tool:

Langfuse tracing:已啟用
Trace ID:4f8c12a8...
意圖:refund
政策結果:allow
原因代碼:OWNER_CONFIRMED
Tool 執行:是
回覆:退款已建立:REF-A1B2C3D4E5F6

2. 執行越權異常請求

執行一筆非本人訂單的越權請求(使用者嘗試退款屬於他人的訂單 ORD-2001):

uv run agent-security-audit \
  "我要退款訂單 ORD-2001" \
  --request-id req-deny-1 \
  --idempotency-key refund-req-deny-1

終端機輸出顯示政策在事前已阻擋該操作,Tool 未被執行:

Langfuse tracing:已啟用
Trace ID:7c3b4a1f...
意圖:refund
政策結果:deny
原因代碼:NOT_OWNER
Tool 執行:否
回覆:退款未執行:NOT_OWNER

稽核紀錄:如何用 Trace 作為合規證據

執行完成後,兩筆請求的完整決策鏈路已記錄在 Langfuse。點入 Langfuse Web UI 的 Tracing 頁面,可以檢視完整的稽核軌跡。

1. 樹狀圖對比:放行鏈路 vs. 阻擋斷鏈

在 Langfuse 的 Tracing 頁面點入這兩筆 Trace,可以直接透過左側樹狀圖對比放行與阻擋的結構差異:

正常放行 Trace(ORD-1001 本人)

點開本人退款請求,左側面板呈現完整的放行鏈路:

  • customer-service-agent(根節點)
    • intent-routing(意圖辨識確認為退款)
    • refund-policy(事前政策檢查,顯示 policy_allowed: True)
    • process-refund(政策放行後建立的 Tool 步驟,完成退款)

https://ithelp.ithome.com.tw/upload/images/20261010/20111896kvYnAU0unS.png

異常攔截 Trace(ORD-2001 越權)

點開非本人退款請求,最關鍵的合規證據在於鏈路在此中斷:

  • 流程在 refund-policy(顯示 policy_allowed: False)後結束。
  • 樹狀圖中完全沒有出現 process-refund 節點。
  • 根節點被打上 unauthorized_access: True 與 policy_outcome: deny 標記。

這直接向審計人員證明:即使模型正確識別出退款意圖,因為事前政策檢查未通過,退款 Tool 根本未被建立或呼叫。

https://ithelp.ithome.com.tw/upload/images/20261010/20111896NVQ9Lthpge.png

2. 身分去識別化(Pseudonymous Identity)

在右側屬性面板中,稽核紀錄需要辨認使用者,但觀測平台不能存放明文個資(如身分證字號或明文 UUID):

  • User ID:顯示為去識別化的別名(例如 usr_3a9f...)。透過 _pseudonymous_user_id() 以 HMAC-SHA256 計算,相同的真實使用者會對應到同一個別名,既能跨 Session 追蹤同一個帳號的行為,又符合資安規範。
  • Session ID:demo-session-1,關聯同一次會話的對話脈絡。
  • Metadata:包含這次請求的 request_id: "req-allow-1",方便與業務系統的日誌交叉比對。

異常監控:如何從海量 Trace 中揪出異常

文字 Metadata 只能在點開單筆 Trace 時人工閱讀,無法直接用來計算次數或即時告警。

Langfuse 的 Score 除了數值(Numeric)之外,也支援布林值(Boolean)。在安全監控中,我們將政策檢查結果以 Boolean 型態的 Score(即安全狀態標記 Flag) 記錄下來,用來驅動即時告警、態勢看板與威脅篩選。

這些安全標記是由 Python 程式碼在 Guardrail 檢查完後確定性寫入,不需要耗費額外 Token 請 AI 評分。

1. 定義安全指標欄位

在 Langfuse 介面進入 Settings → Score Configs 點選 Create Score Config 建立兩個核心安全指標:

  • unauthorized_access(Data Type 選擇 Boolean):True 表示亮紅燈,偵測到非本人訂單越權存取。
  • policy_allowed(Data Type 選擇 Boolean):True 表示亮綠燈,政策檢查放行。

接著在 security_audit/runner.py 的 _score_policy() 中透過 SDK 寫入評分:

def _score_policy(observation: Any, decision: RefundPolicyDecision) -> None:
    # 記錄政策是否放行
    observation.score(
        name="policy_allowed",
        value=1 if decision.outcome is PolicyOutcome.ALLOW else 0,
        data_type="BOOLEAN",
        comment=decision.reason_code.value,
    )
    # 記錄是否為越權存取(寫在整筆 Trace 上便於清單直接篩選)
    observation.score_trace(
        name="unauthorized_access",
        value=1 if decision.reason_code is ReasonCode.NOT_OWNER else 0,
        data_type="BOOLEAN",
    )

點選被攔截 Trace 頂部的 Scores 分頁,即可看到 policy_allowed: False 與 unauthorized_access: True(亮起紅燈標記)。

2. 利用 Tracing Filter 篩選出被攻擊的 Trace

在 Langfuse 的 Tracing 總覽清單中,點擊 Filter 加入篩選條件:

  • Score: unauthorized_access = True

清單會立即過濾掉所有正常的對話,只留下被事前攔截的越權請求。資安值班人員可以直接點擊檢視是哪位使用者、在什麼時間點、嘗試操作哪筆訂單,快速排查攻擊行為。

3. 建立安全監控看板(Dashboard)

累積多筆請求後,可以在 Langfuse 上建立視覺化看板來監控整體異常趨勢:

  1. 點選左側導航欄的 Dashboards,點選 New Dashboard,命名為 Agent Security。
  2. Langfuse 預設會自動產生一張 Scores 總覽彙總卡片,列出所有透過程式碼送出的安全指標:
    • Ⓑ unauthorized_access (api):Ⓑ 代表 Boolean 型態,(api) 代表由程式碼自動打上。
    • Ⓑ policy_allowed (api):統計放行比例。
  3. 若想將特定安全指標拉出成為獨立的趨勢圖或圓餅圖,點選右上角的 Add Widget:
    • 非本人越權嘗試趨勢:
      • Source:Scores
      • Score:unauthorized_access
      • Filter:value = True
      • Chart Type:Time Series 或 Bar chart
    • 政策放行與拒絕比例:
      • Source:Scores
      • Score:policy_allowed
      • Chart Type:Pie chart
  4. 點選 Save 儲存看板。當越權嘗試次數突增或放行率異常下降時,即可在看板中一眼看出異常趨勢。
    https://ithelp.ithome.com.tw/upload/images/20261010/201118962WwvtClxGf.png

4. 對安全指標建立 Alert 即時告警

除了在看板查看圖表,重大資安事件需要即時通報:

  1. 點選左側導航欄的 Settings → Integrations(或 Automations),設定通知目標(例如 Slack Webhook 或自訂 Webhook URL)。
  2. 點選左側導航欄的 Alerts,點選 New Alert:
    • Name:Unauthorized Order Access Attempt
    • Source:Scores
    • Score:unauthorized_access
    • Condition:Count > 0(在 5 分鐘內只要出現一次就觸發)
    • Action:選擇剛設定的 Slack / Webhook 管道。
  3. 點選 Create Alert 啟用規則。

Alert 的職責在於通知值班人員介入調查;程式內部的事前政策檢查(Guardrail)才是阻擋越權的第一道防線,不能等收到告警才在後續補救。

小結

這個系列從發布門檻出發,依序把評估拆成幾層,每一層用不同方式判斷,結果都寫成 Langfuse 的 Score:

  • 確定性行為:意圖分流、邊界防護這類有明確答案的行為,用 pytest 在發布前驗證。
  • Offline Dataset:把固定案例放進 Dataset,每次修改 Prompt、模型或 Graph 都重跑,比對 intent 與 handled_by 是否符合預期。
  • LLM Judge:回覆內容沒有唯一答案時,由 LLM 依評分標準打分,並與人工標註對照,確認評分可信。
  • 線上 Trace:抽樣真實請求,評估意圖分類的正確率,並把人工確認的失敗案例補回 Dataset,成為下一輪的回歸測試。
  • 稽核與異常監控:Guardrail 在 Tool 之前阻擋越權請求,Trace 保存決策鏈路作為稽核證據,unauthorized_access 等 Boolean Score 則支援 Filter、Dashboard 與 Alert。

前四層判斷 Agent 答得對不對,最後一層判斷 Agent 有沒有做不該做的事。Offline 評估在發布前把關,線上評估與監控在發布後持續觀察,兩邊透過失敗案例回流 Dataset 串成同一個循環。


上一篇
用 Langfuse 從線上 Trace 評估 Agent 正確率
下一篇
總結
系列文
AI Agent 系統開發 30 天 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言